#include <iostream>
#include "TH3D.h"
#include "TCanvas.h"
#include "TKey.h"
#include <math.h>
#include "IOmanager.h"
using std::cout;
using std::endl;

IOmanager* IOmanager::_instance = NULL;
TFile*  IOmanager::input     = NULL;
TFile*  IOmanager::output    = NULL;
TTree* IOmanager::ntuple = NULL;

int    IOmanager::ET_VALUE[15]  = { -1, 0 };
int    IOmanager::ETA_VALUE[20] = { -1, 0 };

IOmanager::IOmanager() {
    ntuple = new TTree("Results","Results");
}

IOmanager::~IOmanager() {
    closeOutput();
    closeInput();
    if(input) input->Close();
    _instance=NULL;
}

IOmanager& IOmanager::instance() {
    if(!_instance) return *(_instance = new IOmanager());
    return *_instance;
}

/*******************************************************************************
  I/O pointers management routines
 *******************************************************************************/

void IOmanager::openInput(TString filename) {
    if(input) closeInput();
    input = new TFile(filename,"read");
    if(!input->IsOpen())
    {
        cout<<"IOmanager::openInput: error opening: "<<filename<<endl;
        exit(-1);
    }
    input->cd();
}

void IOmanager::closeInput() {
    if(input)
    {
        input->Close();
        delete input;
        input = NULL;
    }
}

void IOmanager::openOutput(TString filename, TString mode) {
    if(output) closeOutput();
    output = new TFile(filename,mode);
    if(!output->IsOpen()) {
        cout<<"IOmanager::openOutput: error opening: "<<filename<<endl;
        delete output;
        output=NULL;
    }
}

void IOmanager::closeOutput() {
    if(output) {
        save(ntuple);
        output->Write();
        output->Close();
        delete output;
        output = NULL;
    }
}

// Save any TObject into output file
void IOmanager::save(TObject *t) {
    if(!output) {
        cout<<"IOmanager::save: invoke openOutput first"<<endl;
        return;
    }
    output->cd();
    output->WriteObject(t,t->GetName());
}

// Draw RooPlot and save in file
void IOmanager::savePlot(RooPlot* p, bool log, TString dir1,TString dir2,TString dir3,TString dir4,TString dir5,TString dir6) {
    if(!output) {
        cout<<"IOmanager::save: invoke openOutput first"<<endl;
        return;
    }
    TString uniquename = dir1+dir2+dir3+dir4+dir5+dir6+"_"+p->GetName();
    TCanvas  c(uniquename,p->GetName(),1000,1000);
    TObject *o = (TObject*)&c;
    c.cd();

    if(log) {
        p->SetMinimum(1.0);
        gPad->SetLogy(1);
    }

    p->SetTitle(0);
    p->Draw();

    output->cd();

    TDirectory *histo = (TDirectory*)output->Get("histograms");
    if(!histo) histo = output->mkdir("histograms");

    histo->cd();
    o->Write(uniquename);

    TDirectory *d1 = (TDirectory*)histo->Get(dir1);
    if(d1==NULL) d1 = histo->mkdir(dir1);

    TDirectory *d2 = (TDirectory*)d1->Get(dir2);
    if(d2==NULL) d2 = d1->mkdir(dir2);

    TDirectory *d3 = (TDirectory*)d2->Get(dir3);
    if(d3==NULL) d3 = d2->mkdir(dir3);

    TDirectory *d4 = (TDirectory*)d3->Get(dir4);
    if(d4==NULL) d4 = d3->mkdir(dir4);

    TDirectory *d5 = (TDirectory*)d4->Get(dir5);
    if(d5==NULL) d5 = d4->mkdir(dir5);

    TDirectory *d6 = (TDirectory*)d5->Get(dir6);
    if(d6==NULL) d6 = d5->mkdir(dir6);

    d6->WriteObject(o,p->GetName());
}

/*******************************************************************************
  Read ET_VALUES and ETA_VALUES from file
 *******************************************************************************/
void IOmanager::readAxisValuesFromFile(TString filename, int histoNumber) {
    // Prevent reading values more than once
    if(ET_VALUE[0]!=-1 && ETA_VALUE[0]!=-1) return;

    TAxis *axisX=NULL,*axisY=NULL;

    TFile file(filename,"read");
    if(!file.IsOpen()) {
        cout<<"IOmanager::readAxisValues: error opening: "<<filename<<endl;
        exit(-1);
    }

    TString temp="histomap";
    temp+=histoNumber;
    TH3D *histo = (TH3D*)file.Get(temp);

    if(!histo) {
        cout<<"IOmanager::readAxisValues: error retrieving values from file: "<<filename<<endl;
        exit(-1);
    }

    axisX = histo->GetXaxis();
    axisX->SetName("ETA_VALUES");

    axisY = histo->GetYaxis();
    axisY->SetName("ET_VALUES");

    // Get ETA values
    int nbins = axisX->GetNbins();

    ETA_VALUE[0]      =0;
    ETA_VALUE[nbins+1]=(int)floor(axisX->GetBinUpEdge(nbins)*100+0.5);

    for(int i=0;i<=nbins;i++) ETA_VALUE[i]=(int)floor(axisX->GetBinLowEdge(i)*100+0.5);

    //for(int i=1;i<nbins+2;i++) printf("%i\n",ETA_VALUE[i]); 

    // Get ET values
    nbins = axisY->GetNbins();

    ET_VALUE[0]      =0;
    ET_VALUE[nbins+1]=(int)axisY->GetBinUpEdge(nbins);

    for(int i=1;i<=nbins;i++) ET_VALUE[i]=(int)axisY->GetBinLowEdge(i);

    file.Close();
}

/*******************************************************************************
  Return et bin number depending on value
 *******************************************************************************/
int IOmanager::getETbin(int val) {
    if(ET_VALUE[0]==-1) {
        cout<<"IOmanager::getETbin: read values from file first!"<<endl;
        exit(-1);
    }
    int i=0;
    for(;i<15;i++) if(IOmanager::ET_VALUE[i]==val) break;

    if(i>=15) {
        cout<<"IOmanager::getETbin: no bin found for value: "<<val<<endl;
        exit(-1);
    }
    return i;
}

/*******************************************************************************
  Routines for getting histograms from file
 *******************************************************************************/

// Translate integer to string for getProjectionZ
TH1D* IOmanager::getProjectionZ(int o, int eta_min, int eta_max, int bin_min, int bin_max) {
    TString name = "histomap";
    name+=o;

    return getProjectionZ(name,eta_min,eta_max,bin_min,bin_max);
}

// Return projection Z for single TH1D or sum of several TH1D
TH1D* IOmanager::getProjectionZ(TString name, int eta_min, int eta_max, int bin_min, int bin_max)
{
    if(!input) {
        throw AnalysisException("Attempted to read histogram without successfully calling open first");
    }

    TObject *hist = input->Get(name);
    if(!hist)
    {
        cout<<"IOmanager:getProjectionZ: Histogram not found in input: "<<name<<endl;
        exit(-1);
    }

    TH1D *projection = NULL;

    // Special case: full range. In this case, we ommit bins 5 and 14
    if(eta_min == 1 && eta_max == 18)
    {
        TH3D *temp3D = (TH3D *) hist->Clone();

        // Get histograms for three separate parts of eta range
        TH1D *temp1D_1 = (TH1D *) temp3D->ProjectionZ("_pz",  1,  4,  bin_min, bin_max);
        TH1D *temp1D_2 = (TH1D *) temp3D->ProjectionZ("_pz2", 6,  13, bin_min, bin_max);
        TH1D *temp1D_3 = (TH1D *) temp3D->ProjectionZ("_pz3", 15, 18, bin_min, bin_max);

        // Add histograms
        temp1D_1->Add(temp1D_2);
        temp1D_1->Add(temp1D_3);

        projection = (TH1D*) temp1D_1->Clone();

        delete temp3D;
        delete temp1D_1;
        delete temp1D_2;
        delete temp1D_3;
    }
    // Special case: '5 14' means: 'take only bins 5 and 14'
    else if(eta_min==5 && eta_max==14)
    {
        TH3D *temp3D = (TH3D *) hist->Clone();

        // Get histograms for two separate bins
        TH1D *temp1D_1 = (TH1D *) temp3D->ProjectionZ("_pz",  5,  5,  bin_min, bin_max);
        TH1D *temp1D_2 = (TH1D *) temp3D->ProjectionZ("_pz2", 14, 14, bin_min, bin_max);

        // Add histograms
        temp1D_1->Add(temp1D_2);

        projection = (TH1D*) temp1D_1->Clone();

        delete temp3D;
        delete temp1D_1;
        delete temp1D_2;
    }
    // General case
    else
    {
        TH3D *temp3D = (TH3D *) hist->Clone();
        TH1D *temp1D = (TH1D *) temp3D->ProjectionZ("_pz", eta_min, eta_max, bin_min, bin_max);

        projection = (TH1D*) temp1D->Clone();

        delete temp3D;
        delete temp1D;
    }

    return projection;
}

/*******************************************************************************
  Routines for saving to and reading from independent files
 *******************************************************************************/

// Draw canvas on screen and save it to file (with or without log scale)
void IOmanager::drawAndSave(RooPlot *p, TString filename, bool log)
{
    TString cName("canvas");
    cName+=rand();
    TCanvas* c = new TCanvas(cName,filename,600,600) ;
    if(log)
    {
        gPad->SetLogy(1);
        p->SetMinimum(1.0);
    }
    p->SetTitle(0);
    p->Draw() ;
    c->SaveAs(filename);
}

// Return TTree* from file
TTree* IOmanager::getNtuple(TString filename)
{
    TFile *f = new TFile(filename,"read");
    if(!f->IsOpen())
    {
        cout<<"IOmanager::getNtuple: error opening: "<<filename<<endl;
        return NULL;
    }
    TTree *ret=NULL;
    TList *l = f->GetListOfKeys();
    TIter next(l);
    TKey *k=NULL;
    while( (k = (TKey*) next()) )
    {
        if(strcmp(k->GetClassName(),"TTree")!=0) continue;
        if(strcmp(k->GetName(),"Results")!=0)    continue;
        ret = (TTree*) f->Get(k->GetName());
        break;
    }
    return ret;
}
